package com.rongji.cms.tools.temp.xsd;

import cn.hutool.core.util.StrUtil;
import com.alibaba.excel.EasyExcel;
import com.rongji.cms.tools.common.utils.StringUtils;
import org.w3c.dom.*;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * @author yang
 * @date 2023/1/9 11:51
 * @description xsd文件解析
 * <p>
 * 大概的就是按层级循环，然后获取需要的标签属性或者内容
 * 目前只是根据需要的xs:element标签所在层级获取他及下属层级的内容，
 * 并没有说完全解析xsd文件
 */

public class XsdParser {

    public static List<XSDNode> list;
//    static String fileName = "HDFC011";
        static String fileName = "HDFC008";
//        static String fileName = "HDFC109";
    static String readXsdFile = "C:\\xsd\\" + fileName + ".xsd";
    static String outFile = "C:\\xsd\\" + fileName + ".xlsx";
    //    HDFC008，HDFC012这2个文件都是第16层开始才是我们需要的字段
    static int num = 16;

    static {
        if ("ECERT101".equals(fileName)) {
//           ECERT101文件 第四层开始是我们需要的字段
            num = 4;
        }
    }


    public static void main(String[] args) {
        list = new ArrayList<>();
//        File file = new File("C:\\test\\111.xsd");
        File file = new File(readXsdFile);
        (new XsdParser()).readXML(file);

        System.out.println("节点数量：" + list.size());
        for (XSDNode xsdNode : list) {
            System.out.println("-----------" + xsdNode.toString());
        }
        excelWrite(list);
    }

    /**
     * 读取XML(文档对象-根元素节点-所有的Element类型节点-Text类型节点的内容) ;
     * 获取文档对象:DocumentBuilderFactory → DocumentBuilder → Document
     */


    public void readXML(File file) {
        // ❶Ⅰ获得DocumentBuilderFactory
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        try {
            //获得DocumentBuilder
            DocumentBuilder builder = factory.newDocumentBuilder();
            //--获得文档对象--
            Document doc = builder.parse(file);
            //获得根元素
            Element element = doc.getDocumentElement();

            XSDNode xsdNode = new XSDNode();
            //用方法遍历递归打印根元素下面所有的ElementNode(包括属性,TextNode非空的值),用空格分层次显示.
            listAllChildNodes(element, 0, xsdNode);// 参数0表示设定根节点层次为0,它的前面不打印空格.

        } catch (ParserConfigurationException e) {
            e.printStackTrace();

        } catch (SAXException e) {
            e.printStackTrace();

        } catch (IOException e) {
            e.printStackTrace();

        }

    }

    /**
     * 递归遍历并打印所有的ElementNode(包括节点的属性和文本节点的有效内容),按一般的xml样式展示出来(空格来表示层次)
     */


    public void listAllChildNodes(Node node, int level, XSDNode xsdNode) {
        // 只处理ElementNode类型的节点,感觉这种类型的节点(还有有效的文本节点)才是真正有用的数据,其他注释节点,空白节点等都用不上.


        if (node.getNodeType() == Node.ELEMENT_NODE) {
            boolean hasTextChild = false;// 变量表示该节点的第一个子节点是否就是一个有有效内容的文本节点)

            if (node.hasAttributes()) {

                NamedNodeMap nnmap = node.getAttributes();
                for (int i = 0; i < nnmap.getLength(); i++) {
                    String nnmapName = nnmap.item(i).getNodeName();
                    String nnmapValue = nnmap.item(i).getNodeValue();

                    setXsdNode(node, level, xsdNode, nnmapName, nnmapValue);
//                    --------------------------
                }


            }
            // Ⅳ❹【打印 - 子节点】该ElementNode包含子节点时候的处理
            if (node.hasChildNodes()) {
                level++;// 有下一级子节点,层次加1,新的层次表示的是这个子节点的层次(递归调用时传给了它)
                // 获得所有的子节点列表
                NodeList nodelist = node.getChildNodes();
                // 循环遍历取到所有的子节点
                for (int i = 0; i < nodelist.getLength(); i++) {
                    // Ⅳ❹❶【有效文本子节点】子节点为TextNode类型,并且包含的文本内容有效
                    if (nodelist.item(i).getNodeType() == Node.TEXT_NODE
                            && (!nodelist.item(i).getTextContent()
                            .matches("\\s+"))) {// 用正则选取内容包含非空格的有效字符的文本节点
                        hasTextChild = true;// 该ElementNode的一级子节点是存在有效字符的文本节点
//                        System.out.println("nodeTextContent:" + nodelist.item(i).getTextContent());

                        xsdNode.setDocumentation(nodelist.item(i).getTextContent());


                    } else if (nodelist.item(i).getNodeType() == Node.ELEMENT_NODE) {

                        // 递归调用方法 - 以遍历该节点下面所有的子节点
                        listAllChildNodes(nodelist.item(i), level, xsdNode);// level表示该节点处于第几个层次(相应空格)


                    }
//                    子节点循环完后再次回到num层级的时候，就解析完所有的属性了，这时候将xsdNode重新new下
                    if (level == num) {
//                        把节点name为空的排除掉
                        if (StrUtil.isNotEmpty(xsdNode.getElementName())) {
                            list.add(xsdNode);
                        }
                        xsdNode = new XSDNode();
                    }


                }

                level--;// 遍历完所有的子节点,层次变量随子节点的层数,依次递减,回归到该节点本身的层次
                // level++ 和 level--对于该节点的子节点影响的是子节点的初值


            }


        }


    }

    private void setXsdNode(Node node, int level, XSDNode xsdNode, String nnmapName, String nnmapValue) {
        //                    level >= num时才有我们需要的属性
        if (level >= num) {
            //                    xs:element节点有属性
            if ("xs:element".equals(node.getNodeName())) {
                if ("name".equals(nnmapName)) {
                    xsdNode.setElementName(nnmapValue);
                }
                if ("type".equals(nnmapName)) {
//                    xs:base64Binary,去掉xs:
                    xsdNode.setElementType(StringUtils.substring(nnmapValue, 3));
                }
                if ("minOccurs".equals(nnmapName)) {
                    xsdNode.setElementMinOccurs(nnmapValue);
                }
                if ("maxOccurs".equals(nnmapName)) {
                    xsdNode.setElementMaxOccurs(nnmapValue);
                }

            }
//                        --------------------------------------
            //                   xs:restriction 节点有属性
            else if ("xs:restriction".equals(node.getNodeName())) {
                if ("base".equals(nnmapName)) {
//                   xs:string,去掉xs:
                    xsdNode.setRestrictionBase(StringUtils.substring(nnmapValue, 3));
                }
            }
//                        --------------------------------------
//                        xs:maxLength
            //                    xs:maxLength节点有属性
            else if ("xs:maxLength".equals(node.getNodeName())) {
                xsdNode.setRestrictionName("maxLength");
                if ("value".equals(nnmapName)) {
                    xsdNode.setRestrictionValue(nnmapValue);
                }
//                            xs:length 节点有属性
            } else if ("xs:length".equals(node.getNodeName())) {
                xsdNode.setRestrictionName("length");
                if ("value".equals(nnmapName)) {
                    xsdNode.setRestrictionValue(nnmapValue);
                }
//                            xs:minLength 节点有属性
            } else if ("xs:minLength".equals(node.getNodeName())) {
                xsdNode.setRestrictionName("minLength");
                if ("value".equals(nnmapName)) {
                    xsdNode.setRestrictionValue(nnmapValue);
                }
            }

        }
    }


    public static void excelWrite(List<XSDNode> list) {
        //1、创建一个文件对象
        File excelFile = new File(outFile);
        //2、判断文件是否存在，不存在则创建一个Excel文件
        if (!excelFile.exists()) {
            try {
                excelFile.createNewFile();//创建一个新的文件
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        //3、指定需要那个class去写。然后写到第一个sheet，名字为模版，然后文件流会自动关闭
        EasyExcel.write(excelFile, XSDNode.class).sheet("字段").doWrite(list);
        System.out.println("导出完成。。。。");
    }

}
